<template>
  <el-dialog
    v-model="dialogVisible"
    title="文件上传"
    :draggable="true"
    :fullscreen="isMobile"
  >
    <div class="title">
      <el-alert
        :title="`单个文件不能超过${formatFileSize(
          MAX_FILE_SIZE
        )}, 每次最多只能上传${MAX_FILE_COUNT}个文件。`"
        type="warning"
        show-icon
        :closable="false"
      />
      <input
        type="file"
        ref="fileInput"
        @change="handleFileChange"
        multiple
        style="display: none"
        accept="*"
      />
      <el-button type="primary" @click="triggerFileInput">选择文件</el-button>
    </div>

    <el-table
      :data="taskList"
      empty-text="将文件拖到此处上传"
      row-key="filename"
      height="400px"
      :summary-method="getSummaries"
      show-summary
      @dragover.prevent="onDragOver"
      @dragleave.prevent="onDragLeave"
      @drop.prevent="onDrop"
    >
      <!-- 文件图标 -->
      <el-table-column prop="isFolder" label="类型" width="55">
        <template #default="{ row }">
          <svg class="file-icon" aria-hidden="true">
            <use :xlink:href="`#${getFileIconId(row.type)}`" />
          </svg>
        </template>
      </el-table-column>
      <!-- 文件名 -->
      <el-table-column prop="isFolder" label="文件名" min-width="200">
        <template #default="{ row }">
          <span>
            {{ row.filename }}
          </span>
          <p>{{ row.uploadedSize }}/{{ row.size }}</p>
          <el-progress
            :percentage="parseInt((row.uploadedSize / row.size) * 100) || 0"
          />
        </template>
      </el-table-column>
      <!-- 文件大小 -->
      <el-table-column prop="size" label="文件大小" v-if="!isFold">
        <template #default="{ row }">
          {{ formatFileSize(row.size) }}
        </template>
      </el-table-column>
      <!-- 上传状态 -->
      <el-table-column label="状态" v-if="!isFold">
        <template #default="{ row }">
          <el-tag :type="getStatusTagType(row.status)">{{ row.status }}</el-tag>
        </template>
      </el-table-column>
      <!-- 操作 -->
      <el-table-column label="操作" v-if="!isFold">
        <template #default="{ row }">
          <el-button link type="info" size="small" @click="onDelete(row)">{{
            getButtonText(row.status)
          }}</el-button>
        </template>
      </el-table-column>
      <!-- 展开折叠 -->
      <el-table-column type="expand" v-if="isFold">
        <template #default="props">
          <div class="expand-content">
            {{ formatFileSize(props.row.size) }}
            <el-tag :type="getStatusTagType(props.row.status)">{{
              props.row.status
            }}</el-tag>
            <el-button
              link
              type="info"
              size="small"
              @click="onDelete(props.row)"
              >{{ getButtonText(props.row.status) }}</el-button
            >
          </div>
        </template>
      </el-table-column>
    </el-table>
    <template #footer>
      <el-button @click="dialogVisible = false">关闭窗口</el-button>
      <el-button
        type="primary"
        @click="onConfirm"
        :disabled="taskList.length === 0"
      >
        开始上传
      </el-button>
    </template>
  </el-dialog>
</template>
<script setup>
import axios from "axios";
import { ref, computed } from "vue";
import { useWindowSize } from "@vueuse/core";
import { formatFileSize } from "../utils/FormatUtil";
import { getFileIconId } from "../utils/FileIconId";
import { ElMessage } from "element-plus";
import { verifyUploadApi, mergeFileApi, getUploadSizeLimitApi  } from "../api/index";
import { useStore } from "../stores";
import SparkMD5 from "spark-md5";

const emits = defineEmits(["onUploadSuccess"]);
const { width } = useWindowSize();
const isMobile = computed(() => width.value < 768);
const isFold = computed(() => width.value < 900);
const store = useStore();

const dialogVisible = ref(false);
const fileInput = ref(null);
const taskList = ref([]);
const isUploading = ref(false);
const isDragOver = ref(false);

let parentId = null;

// 最大文件大小 (20MB)  最大文件数量
let MAX_FILE_SIZE = 20 * 1024 * 1024;
const MAX_FILE_COUNT = 10;
const CHUNK_SIZE = 1 * 1024 * 1024; // 1MB分片

const triggerFileInput = () => fileInput.value.click();

const getButtonText = (status) => {
  switch (status) {
    case "已取消":
      return "继续";
    case "上传失败":
      return "取消";
    case "上传成功":
      return "完成";
    default:
      return "取消";
  }
};
// 处理拖拽进入
const onDragOver = () => {
  isDragOver.value = true;
};

// 处理拖拽离开
const onDragLeave = () => {
  isDragOver.value = false;
};

// 处理文件放置
const onDrop = (e) => {
  isDragOver.value = false;
  const ee = {
    target: {
      files: e.dataTransfer.files,
    },
  };
  handleFileChange(ee);
};

// 选择文件
const handleFileChange = async (e) => {
  const files = Array.from(e.target.files);

  if (!files || files.length === 0) {
    ElMessage.warning("请选择文件");
    return;
  }

  if (files.length + taskList.value.length > MAX_FILE_COUNT) {
    ElMessage.warning(`最多只能上传${MAX_FILE_COUNT}个文件`);
    return;
  }

  for (const file of files) {
    if (file.size > MAX_FILE_SIZE) {
      ElMessage.warning(
        `文件 ${file.name} 超过 ${formatFileSize(MAX_FILE_SIZE)}限制，已略过`
      );
      continue;
    }
    taskList.value.push({
      parentId: parentId,
      status: "待上传",
      filename: file.name,
      uploadedSize: 0,
      size: file.size,
      type: file.type,
      rawFile: file,
      controller: null, // 用于保存AbortController
      hash: null, // 文件hash值
      chunks: null, // 文件分片列表
    });
  }

  e.target.value = "";
};

// 删除文件
const onDelete = async (row) => {
  // 尝试恢复上传
  if (row.status === "已取消") {
    console.log("恢复上传");
    uploadFile(row);
    return;
  }
  // 取消上传
  const index = taskList.value.findIndex(
    (item) => item.filename === row.filename
  );
  if (index !== -1) {
    const task = taskList.value[index];
    if (task.controller) {
      task.controller.abort(); // 中止请求
    }
    if (task.status === "上传中") {
      task.status = "已取消";
    } else {
      taskList.value.splice(index, 1);
    }

    if (task.status !== "上传成功") {
      ElMessage.warning(`已取消上传 ${task.filename}`);
    }
  }
};

// 获取状态标签类型
const getStatusTagType = (status) => {
  switch (status) {
    case "准备中":
      return "warning";
    case "上传中":
      return "primary";
    case "上传成功":
      return "success";
    case "上传失败":
      return "danger";
    default:
      return "info";
  }
};

// 上传文件
const onConfirm = async () => {
  if (isUploading.value) return;
  for (const task of taskList.value) {
    if (task.status === "上传成功") continue;
    task.status = "准备中";
    await uploadFile(task);
  }
};

const uploadFile = async (task) => {
  try {
    task.chunks = createFileChunks(task.rawFile);
    task.hash = await calcuteHash(task.chunks);

    const data = await verifyUploadApi(task.hash, task.filename);
    if (data.isUploaded) {
      await mergeFileApi(
        task.hash,
        task.filename,
        task.size,
        task.type,
        task.parentId
      );
      task.uploadedSize = task.size;
      task.status = "上传成功";
      ElMessage.success(`${task.filename} 秒传成功`);
      emits("onUploadSuccess");
      return;
    }
    // 计算已上传大小
    const uploadedSize = data.chunks.length * CHUNK_SIZE;
    console.log('上传文件大小',uploadedSize);
    task.uploadedSize = uploadedSize;
    task.status = "上传中";
    task.controller = new AbortController();

    uploadChunks(task, data.chunks);
  } catch (error) {
    console.error("上传失败", error);
    task.status = "上传失败";
  }
};

const createFileChunks = (file) => {
  let cur = 0;
  const chunks = [];
  while (cur < file.size) {
    chunks.push(file.slice(cur, cur + CHUNK_SIZE));
    cur += CHUNK_SIZE;
  }
  return chunks;
};

const calcuteHash = (chunks) => {
  // 计算策略
  // 1. 第一个和最后一个切片全部参与计算
  // 2. 中间的切片只有前两个字节、中间两个字节、后面两个字节参与计算
  return new Promise((resolve) => {
    const targets = [];
    const spark = new SparkMD5.ArrayBuffer();
    chunks.forEach((chunk, index) => {
      if (index === 0 || index === chunks.length - 1) {
        targets.push(chunk);
      } else {
        targets.push(chunk.slice(0, 2)); // 前两个字节
        targets.push(chunk.slice(CHUNK_SIZE / 2, CHUNK_SIZE / 2 + 2)); // 中间两个字节
        targets.push(chunk.slice(CHUNK_SIZE - 2, CHUNK_SIZE)); // 后面两个字节
      }
    });

    const fileReader = new FileReader();
    fileReader.readAsArrayBuffer(new Blob(targets));

    fileReader.onload = (e) => {
      spark.append(e.target.result);
      resolve(spark.end());
    };
  });
};

// 分片上传
const uploadChunks = async (task, uploadedChunks) => {
  const hash = task.hash;
  const chunks = task.chunks;

  const data = chunks.map((chunk, index) => {
    return {
      hash,
      chunkHash: hash + "-" + index,
      filename: task.filename,
      chunk,
      size: task.size,
    };
  });

  const formDatas = data
    .filter((item) => !uploadedChunks.includes(item.chunkHash))
    .map((item) => {
      const formData = new FormData();
      formData.append("hash", item.hash);
      formData.append("chunkHash", item.chunkHash);
      formData.append("filename", item.filename);
      formData.append("chunk", item.chunk);
      formData.append("size", item.size);

      return formData;
    });

  const max = 6; // 最大并行请求数
  const requestPool = []; // 请求队列
  const token = localStorage.getItem("token");
  let index = 0;

  while (index < formDatas.length) {
    if (task.status === "已取消") {
      throw new DOMException("AbortError", "AbortError");
    }
    const CHUNK_UPLOAD_URL = import.meta.env.VITE_BASE_URL + "api/upload/file-chunk"
    const request = axios.post(
      CHUNK_UPLOAD_URL,
      formDatas[index],
      {
        signal: task.controller.signal,
        headers: {
          "Content-Type": "multipart/form-data",
          Authorization: `Bearer ${token}`,
        },
      }
    );
    request.then((res) => {
      // 执行完后把当前任务从任务队列中删除
      const uploadedSize = Math.min(CHUNK_SIZE, task.size - task.uploadedSize);
      task.uploadedSize += uploadedSize;
      requestPool.splice(requestPool.findIndex((item) => item === request));
    });
    request.catch = (error) => {
      if (error.name !== "AbortError") {
        task.status = "上传失败";
        console.error("上传失败:", error);
      }
    };
    request.finally = () => {
      activeUploads.value.delete(task.filename);
    };

    requestPool.push(request);
    if (requestPool.length === max) {
      await Promise.race(requestPool);
    }
    index++;
  }

  await Promise.all(requestPool);

  try {
    const res = await mergeFileApi(
      task.hash,
      task.filename,
      task.size,
      task.type,
      task.parentId
    );
    task.uploadedSize = task.size;
    task.status = "上传成功";
    emits("onUploadSuccess");
    console.log(res);
  } catch (error) {
    console.error("合并文件失败:", error);
  }
};

const getSummaries = (param) => {
  const { columns, data } = param;
  const sums = [];
  columns.forEach((column, index) => {
    if (index === 0) {
      sums[index] = "总计";
      return;
    }
    if (index === 1) {
      sums[index] = data.length + "个文件";
      return;
    }
    if (index === 2) {
      const values = data.map((item) => item.size || 0);
      sums[index] = formatFileSize(
        values.reduce((prev, curr) => prev + curr, 0)
      );
      return;
    }
    if (index === 4) {
      const space = store.userInfo?.storage - store.userInfo?.used;
      sums[index] = "您剩余的空间为" + formatFileSize(space);
      return;
    }
    sums[index] = "";
  });

  return sums;
};

const open = async (id) => {
  const config = await getUploadSizeLimitApi();
  parentId = id;
  MAX_FILE_SIZE = config?.uploadSizeLimit ? config.uploadSizeLimit * 1024 * 1024 : 64 * 1024 * 1024
  dialogVisible.value = true;
};
const close = () => {
  dialogVisible.value = false;
};
defineExpose({
  open,
  close,
});
</script>
<style scoped>
.title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
  margin-bottom: 10px;
}

.expand-content {
  display: flex;
  justify-content: space-between;
}
</style>
